#include "proxy/encry/encryserverconn.h"

namespace proxy{

  EncryServerConn::EncryServerConn(socket_ptr connect, CurlClientSession::Ptr curl_session)
    :client_socket_(connect),
    io_service_(connect->get_io_service()),
    key_('G'),
    header_size_(0),
    is_write_shake_(false),
    is_read_shake_(false),
    server_socket_(connect->get_io_service()),
    curl_session_(curl_session){

  }

  EncryServerConn::~EncryServerConn(){
    LOG(WARNING) << "Delete";
  }

  void EncryServerConn::Start(std::string addr, uint16 port){

    boost::asio::ip::tcp::endpoint server_addr(
      boost::asio::ip::address().from_string(addr), port);

    server_socket_.async_connect(server_addr,
      boost::bind(&EncryServerConn::HandleProxyConnect, shared_from_this(),
      boost::asio::placeholders::error));
  }

  void EncryServerConn::HandleReadEncryData(
    const boost::system::error_code& err, int size){
    if (err){
      //LOG(ERROR) << "Read Encry Shake error";
      server_socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
      return;
    }
    curl_session_->AddFlow(size);
    //LOG(INFO).write(client_buffer_, size);
    if (!is_read_shake_){
      int res = AnalyzeHttpEncryKey(client_buffer_, header_size_ + size);
      if (res == -1){
        header_size_ += size;
        client_socket_->async_read_some(
          boost::asio::buffer(
          client_buffer_ + header_size_, MAX_BUFFER_SIZE - header_size_),
          boost::bind(&EncryServerConn::HandleReadEncryData, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
      }
      else if (res == -2){
        Respone503Error();
      }
      else{
        is_read_shake_ = true;
        int remain_size = header_size_ + size - res;
        if (remain_size > 0){
          std::memmove(client_buffer_, client_buffer_ + res, remain_size);
          ProcessReadData(remain_size);
        }
      }
    }
    else{
      ProcessReadData(size);
    }
  }


  void EncryServerConn::ProcessReadData(int size){
    DecodeData(client_buffer_, size, key_);
    //LOG(INFO).write(client_buffer_, size);

    boost::asio::async_write(server_socket_, boost::asio::buffer(client_buffer_, size),
      boost::bind(&EncryServerConn::HandleServerWrite, shared_from_this(),
      boost::asio::placeholders::error));
  }


  void EncryServerConn::WriteEncryData(char*data, int size){
    curl_session_->AddFlow(size);
    if (!is_write_shake_){
      std::vector<boost::asio::const_buffer> buffers;
      int r = rand() % ENCRY_REPLY_SIZE;
      buffers.push_back(boost::asio::buffer(
        ENCRY_REPLY[r].encry_reply, ENCRY_REPLY[r].encry_reply_size));
      EncryData(data, size, key_);
      buffers.push_back(boost::asio::buffer(data, size));
      boost::asio::async_write(*client_socket_, buffers,
        boost::bind(&EncryServerConn::HandleWriteEncryData, shared_from_this(),
        boost::asio::placeholders::error));
      is_write_shake_ = true;
    }
    else{
      EncryData(data, size, key_);
      boost::asio::async_write(*client_socket_, boost::asio::buffer(data, size),
        boost::bind(&EncryServerConn::HandleWriteEncryData, shared_from_this(),
        boost::asio::placeholders::error));
    }
  }

  void EncryServerConn::HandleWriteEncryData(const boost::system::error_code& err){
    if (err){
      LOG(ERROR) << err.message();
      server_socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
      return;
    }
    ReadServerData();
  }

  void EncryServerConn::ReadMoreEncryData(){
    client_socket_->async_read_some(
      boost::asio::buffer(client_buffer_, MAX_BUFFER_SIZE),
      boost::bind(&EncryServerConn::HandleReadEncryData, shared_from_this(),
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred));
  }

  //void EncryServerConn::HandleReadEncryData(
  //  const boost::system::error_code& err, int size){
  //  if (err){
  //    LOG(ERROR) << "Read data error";
  //    return;
  //  }
  //  ProcessReadData(size);
  //  WriteEncryData(buffer_, size);
  //}

  // 
  void EncryServerConn::HandleProxyConnect(const boost::system::error_code& err){
    if (err){
      LOG(ERROR) << err.message();
      Respone503Error();
      return;
    }
    ReadMoreEncryData();
    ReadServerData();
  }

  void EncryServerConn::Respone503Error(){
    reply_ = Reply::stock_reply(Reply::service_unavailable);
    boost::asio::async_write(*client_socket_, reply_.to_buffers(),
      boost::bind(&EncryServerConn::HandleClientWriteClose, shared_from_this(),
      boost::asio::placeholders::error));
  }

  void EncryServerConn::HandleClientWriteClose(boost::system::error_code err){
    client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both, err);
  }

  void EncryServerConn::HandleServerWrite(boost::system::error_code err){
    if (err){
      LOG(ERROR) << err.message();
      client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both, err);
      return;
    }
    ReadMoreEncryData();
  }

  void EncryServerConn::ReadServerData(){
    server_socket_.async_read_some(
      boost::asio::buffer(server_buffer_, MAX_BUFFER_SIZE),
      boost::bind(&EncryServerConn::HandleReadServerData, shared_from_this(),
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred));
  }

  void EncryServerConn::HandleReadServerData(
    const boost::system::error_code& err, int size){
    if (err){
      LOG(ERROR) << err.message();
      client_socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
      return;
    }
    curl_session_->AddFlow(size);
    WriteEncryData(server_buffer_, size);
  }

  int EncryServerConn::AnalyzeHttpEncryKey(const char *buffer, int size){
    // Find the \r\n\r\n
    for (int i = 0; i < size - ENCRY_END_SIZE; i++){
      if (i > MAX_ENCRY_HEADER_SIZE){
        return -2;
      }
      if (buffer[i] == HTTP_KEY){
        key_ = buffer[i + 1];
      }
      if (strncmp(buffer + i, ENCRY_END_CHAR_SET, ENCRY_END_SIZE) == 0){
        return i + ENCRY_END_SIZE;
      }
    }
    return -1;
  }

}